'use strict';
const path = require("path")
const fs = require("fs")

const fse = require('fs-extra');
const userHome = require('user-home');
const ejs = require('ejs');
const glob = require('glob')
const semver = require('semver');
const inquirer = require('inquirer');

const Command = require('@liangshaojie/command');
const log = require('@liangshaojie/log');
const Package = require('@liangshaojie/package');
const {
    spinnerStart,
    sleep,
    execCommand
} = require('@liangshaojie/utils');

const getProjectTemplate = require('./getProjectTemplate');

const TYPE_PROJECT = 'project';
const TYPE_COMPONENT = 'component';

const TEMPLATE_TYPE_NORMAL = 'normal'
const TEMPLATE_TYPE_CUSTOM = 'custom'

// 只要是继承了Command  自动调用init方法和exec方法
class InitCommand extends Command {
    init() {
        this.projectName = this._argv[0] || '';
        this.projectPath = this._argv[1] && this._argv[1].packagePath || ''
        this.force = this._argv[1] && this._argv[1].force || false
        log.verbose('projectName', this.projectName);
        log.verbose('force', this.force);
    }

    async exec() {
        try {
            // 1. 准备阶段
            const projectInfo = await this.prepare();
            // 下载模板
            if (projectInfo) {
                // 2. 下载模板
                log.verbose('projectInfo', projectInfo);
                this.projectInfo = projectInfo;
                await this.downloadTemplate();
                // 3. 安装模板
                await this.installTemplate()
            }
        } catch (e) {
            log.error('error', e.message);
            if (process.env.LOG_LEVEL === 'verbose') {
                console.log(e)
            }
        }
    }

    // 安装模板
    async installTemplate() {
        if (this.templateInfo) {
            if (!this.templateInfo.type) {
                this.templateInfo.type = TEMPLATE_TYPE_NORMAL
            }
            if (this.templateInfo.type === TEMPLATE_TYPE_NORMAL) {
                await this.installNormalTemplate()
            } else if (this.templateInfo.type === TEMPLATE_TYPE_CUSTOM) {
                await this.installCustomTemplate()
            } else {
                throw new Error('无法识别项目类型')
            }
        } else {
            throw new Error('项目模板信息不存在!')
        }
    }

    // 安装自定义模板
    async installCustomTemplate() {
        if (await this.templateNpm.exists()) {
            const rootFile = this.templateNpm.getRootFilePath();
            if (fs.existsSync(rootFile)) {
                log.notice('开始执行自定义模板');
                const options = {
                    ...this.templateInfo,
                    targetPath: process.cwd(),
                    ...this.projectInfo,
                    sourcePath: path.resolve(this.templateNpm.cacheFilePath, 'template'),
                };
                console.info(options);
                const code = `require('${rootFile}')(${JSON.stringify(options)})`;
                log.verbose('自定义模板传入参数为:', options);
                await execCommand('node', ['-e', code], {
                    stdio: 'inherit',
                    cwd: process.cwd(),
                });
            } else {
                throw new Error('自定义模板入口文件不存在');
            }
        } else {
            throw new Error('模板信息异常');
        }
    }

    // 安装正常模板
    async installNormalTemplate() {
        // 拷贝模板代码至当前目录
        let spinner = spinnerStart('正在安装模板')
        await sleep()
        const projectPath = this.projectPath || process.cwd()
        // 安装的过程其实就是拷贝的过程
        try {
            const templatePath = path.resolve(this.templateNpm.cacheFilePath, 'template')
            fse.ensureDirSync(templatePath)
            fse.ensureDirSync(projectPath)
            fse.copySync(templatePath, projectPath)
        } catch (e) {
            throw e
        } finally {
            spinner.stop(true)
        }
        const templateIgnore = this.templateInfo.ignore || []
        const ignore = ['**/node_modules/**', ...templateIgnore]
        await this.ejsRender({
            ignore
        })
        // 安装依赖文件
        log.notice('init', '开始安装依赖');
        await this.runNpminstall(projectPath);
        log.success('init', '依赖安装成功');
        // {
        //     name: '「慕课乐高」标准项目模板',
        //         npmName: '@imooc-cli/imooc-lego-standard-template',
        //     version: '1.0.0',
        //     type: 'normal',
        //     installCommand: 'npm install',
        //     startCommand: 'npm run serve',
        //     ignore: [ '**/public/**' ],
        //     tag: [ 'project' ],
        //     buildPath: 'dist'
        // }
        // 启动代码
        if (this.templateInfo.startCommand) {
            log.notice('init', '开始执行启动命令');
            const startCommand = this.templateInfo.startCommand.split(' ');
            await this.execStartCommand(projectPath, startCommand);
        }
    }

    // 运行启动命令
    async execStartCommand(targetPath, startCommand) {
        return new Promise((resolve, reject) => {
            const p = execCommand(startCommand[0], startCommand.slice(1), {
                stdio: 'inherit',
                cwd: targetPath
            });
            p.on('error', e => {
                reject(e);
            });
            p.on('exit', c => {
                resolve(c);
            });
        });
    }

    // 运行安装命令
    async runNpminstall(targetPath) {
        return new Promise((resolve, reject) => {
            const p = execCommand('npm', ['install', '--registry=https://registry.npm.taobao.org'], {
                stdio: 'inherit',
                cwd: targetPath
            });
            p.on('error', e => {
                reject(e);
            });
            p.on('exit', c => {
                resolve(c);
            });
        });
    }

    // 使用ejs 渲染文件
    async ejsRender(options) {
        const dir = this.projectPath || process.cwd()
        this.projectInfo = {
            ...this.projectInfo,
            BASE_URL: 'baseUrl',
            webpackConfig: {
                name: 'test'
            }
        }
        return new Promise((resolve, reject) => {
            glob('**', {
                cwd: dir,
                ignore: options.ignore,
                nodir: true // 不选择文件夹
            }, (err, files) => {
                if (err) {
                    reject(err)
                }
                Promise.all(files.map(file => {
                    const filePath = path.join(dir, file)
                    return new Promise((resolve1, reject1) => {
                        ejs.renderFile(filePath, this.projectInfo, {}, (err, result) => {
                            if (err) {
                                reject1(err)
                            } else {
                                fse.writeFileSync(filePath, result)
                                resolve1(result)
                            }
                        })
                    })
                })).then(() => {
                    resolve()
                }).catch(err => {
                    reject(err)
                })
            })
        })
    }

    // 下载模板
    async downloadTemplate() {
        const {
            projectTemplate
        } = this.projectInfo;
        // 获取模板信息
        const templateInfo = this.template.find(item => item.npmName === projectTemplate);
        // 下载模板存放的目录
        const targetPath = path.resolve(userHome, '.pk-lsj-cli', 'template');
        // 模板依赖安装目录
        const storeDir = path.resolve(userHome, '.pk-lsj-cli', 'template', 'node_modules');
        const {
            npmName,
            version
        } = templateInfo;
        this.templateInfo = templateInfo
        
        // 创建一个package实例
        const templateNpm = new Package({
            targetPath,
            storeDir,
            packageName: npmName,
            packageVersion: version,
        });

        if (!await templateNpm.exists()) {
            const spinner = spinnerStart('正在下载模板...');
            await sleep();
            try {
                // 安装包
                await templateNpm.install();
            } catch (e) {
                throw e;
            } finally {
                spinner.stop(true);
                if (await templateNpm.exists()) {
                    log.success('下载模板成功');
                    this.templateNpm = templateNpm
                }
            }
        } else {
            const spinner = spinnerStart('正在更新模板...');
            await sleep();
            try {
                await templateNpm.update();
            } catch (e) {
                throw e;
            } finally {
                spinner.stop(true);
                if (await templateNpm.exists()) {
                    this.templateNpm = templateNpm
                    log.success('更新模板成功');
                }
            }
        }
    }

    // 运行直线检查
    async prepare() {
        // 0. 判断项目模板是否存在
        const template = await getProjectTemplate() || [];
        if (!template || template && template.length === 0) {
            throw new Error('项目模板不存在');
        }
        this.template = template;
        // 获取执行命令的路径
        const localPath = this.projectPath || process.cwd();

        // 判断当前目录是否为空
        if (!this.isDirEmpty(localPath)) {
            let ifContinue = false;
            if (!this.force) {
                // 询问是否继续创建
                ifContinue = (await inquirer.prompt({
                    type: 'confirm',
                    name: 'ifContinue',
                    default: false,
                    message: '当前文件夹不为空，是否继续创建项目？',
                })).ifContinue;
                if (!ifContinue) {
                    return;
                }
            }
            // 2. 是否启动强制更新
            if (ifContinue || this.force) {
                // 给用户做二次确认
                const {
                    confirmDelete
                } = await inquirer.prompt({
                    type: 'confirm',
                    name: 'confirmDelete',
                    default: false,
                    message: '是否确认清空当前目录下的文件？',
                });
                if (confirmDelete) {
                    // 清空当前目录
                    fse.emptyDirSync(localPath);
                }
            }
        }

        return this.getProjectInfo();

    }

    // 获取项目信息
    async getProjectInfo() {
        function isValidName(v) {
            return /^[a-zA-Z]+([-][a-zA-Z][a-zA-Z0-9]*|[_][a-zA-Z][a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(v)
        }
        let projectInfo = {};
        let isProjectNameValid = false
        // 验证项目名称
        if (isValidName(this.projectName)) {
            isProjectNameValid = true
            projectInfo.projectName = this.projectName
        }
        // 选择创建项目的类型
        const {
            type
        } = await inquirer.prompt({
            type: 'list',
            name: 'type',
            message: '请选择初始化类型',
            default: TYPE_PROJECT,
            choices: [{
                name: '项目',
                value: TYPE_PROJECT,
            }, {
                name: '组件',
                value: TYPE_COMPONENT,
            }],
        });
        log.verbose('type', type);
        this.template = this.template.filter(template => template.tag.includes(type))
        const title = type === TYPE_PROJECT ? '项目' : '组件'
        const projectNamePrompt = {
            type: 'input',
            name: 'projectName',
            message: `请输入${title}名称`,
            default: '',
            validate: function (v) {
                const done = this.async();
                setTimeout(function () {
                    // 1.首字符必须为英文字符
                    // 2.尾字符必须为英文或数字，不能为字符
                    // 3.字符仅允许"-_"
                    if (!isValidName(v)) {
                        done(`请输入合法的${title}名称`);
                        return;
                    }
                    done(null, true);
                }, 0);
            },
            filter: function (v) {
                return v;
            },
        }
        const projectPrompt = []
        if (!isProjectNameValid) {
            projectPrompt.push(projectNamePrompt)
        }
        projectPrompt.push({
            type: 'input',
            name: 'projectVersion',
            message: `请输入${title}版本号`,
            default: '1.0.0',
            validate: function (v) {
                const done = this.async();
                setTimeout(function () {
                    if (!(!!semver.valid(v))) {
                        done('请输入合法的版本号');
                        return;
                    }
                    done(null, true);
                }, 0);
            },
            filter: function (v) {
                if (!!semver.valid(v)) {
                    return semver.valid(v);
                } else {
                    return v;
                }
            },
        }, {
            type: 'list',
            name: 'projectTemplate',
            message: `请选择${title}模板`,
            choices: this.createTemplateChoice(),
        })

        if (type === TYPE_PROJECT) {
            // 2. 获取项目的基本信息
            const project = await inquirer.prompt(projectPrompt);
            projectInfo = {
                ...projectInfo,
                type,
                ...project,
            };
        } else if (type === TYPE_COMPONENT) {

        }
        // 生成className
        if (projectInfo.projectName) {
            projectInfo.name = projectInfo.projectName
            projectInfo.className = require('kebab-case')(projectInfo.projectName).replace(/^-/, '')
        }

        if (projectInfo.projectVersion) {
            projectInfo.version = projectInfo.projectVersion
        }

        if (projectInfo.componentDescription) {
            projectInfo.description = projectInfo.componentDescription
        }
        return projectInfo;
    }

    // 创建模板选择内容
    createTemplateChoice() {
        return this.template.map(item => ({
            value: item.npmName,
            name: item.name,
        }));
    }

    // 是否是空文件夹
    isDirEmpty(localPath) {
        let fileList = fs.readdirSync(localPath);
        // 文件过滤的逻辑
        fileList = fileList.filter(file => (
            !file.startsWith('.') && ['node_modules'].indexOf(file) < 0
        ));
        return !fileList || fileList.length <= 0;
    }

}

// 入口函数
function init(argv) {
    return new InitCommand(argv);
}

// 导出入口
module.exports = init;
